In this Solidity example, we explore how to manage arrays within a smart contract.
Declaring Arrays:
arr_1: A dynamic array of unsigned integers (uint256[]), initially empty.arr_2: Another dynamic array, initialized with three values [3, 2, 4].arr_3: A fixed-size array of 5 uint256 elements, but uninitialized.Getting Value from an Array:
The getValueOfIndex function allows the user to retrieve an element from arr_2 at a specific index.
Example of adding a check:
require(_index < arr_2.length, "Index out of bounds");
Adding to the Array:
The addToArray function demonstrates how to dynamically add elements to arr_2 using the push() method. This method appends a new value to the end of the array.
arr_3) don’t support push().In this example, we’re exploring how to use constructors and inheritance in Solidity.
Constructor in MyConstructor Contract:
name with the value passed during deployment.constructor(string memory _name) {
name = _name;
}
Creating Getter and Setter Functions:
To access and modify the name variable, we’ll create getter and setter functions.
name.function getName() public view returns (string memory) {
return name;
}
name.function setName(string memory _newName) public {
name = _newName;
}
Inheritance in MySecondContract:
The MySecondContract inherits from MyConstructor. It calls the parent contract’s constructor to initialize the name variable using the MyConstructor(_name) syntax.
MySecondContract simply passes the provided _name to the parent MyConstructor's constructor.constructor(string memory _name) MyConstructor(_name) {}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyConstructor {
string public name;
constructor(string memory _name) {
name = _name;
}
// Getter function for 'name'
function getName() public view returns (string memory) {
return name;
}
// Setter function for 'name'
function setName(string memory _newName) public {
name = _newName;
}
}
contract MySecondContract is MyConstructor {
constructor(string memory _name) MyConstructor(_name) {}
}
MySecondContract inherits MyConstructor, reusing its functionality.name variable.This Solidity example demonstrates how one contract can interact with another by calling its functions.
Contract MyOtherContract:
age, initialized to 29.getAge() that returns the value of age.uint256 public age = 29;
function getAge() public view returns (uint256) {
return age;
}
Contract CONTRACTB:
getAgeFromOtherContract function allows CONTRACTB to interact with another contract (MyOtherContract) by using its address.MyOtherContract instance, cast it as MyOtherContract, and call its getAge() function.Key steps:
_contractAddress) to interact with its public functions.getAgeFromOtherContract retrieves the age from the other contract.function getAgeFromOtherContract(address _contractAddess) public view returns (uint256) {
MyOtherContract other = MyOtherContract(_contractAddess);
uint256 age = other.getAge();
return age;
}
MyOtherContract(_contractAddress) casts the provided address to a type of the MyOtherContract so that you can call its functions.If you deploy MyOtherContract and pass its address to CONTRACTB, the latter can fetch the age value from MyOtherContract and return it.
In this Solidity example, we will learn how to use require, revert, and assert to handle errors and add necessary checks to a contract. Additionally, we’ll implement a time-based restriction for a function.
Require Statement:
require function ensures that certain conditions are met before the execution of the function proceeds.myFunc, we check if _x is smaller than _y. If not, an error message "X is bigger than Y" is thrown.require(_x < _y, "X is bigger that Y");
Revert:
revert function is used to explicitly abort a function when a certain condition is met.myPureRevertFunc, if _x == 10, the transaction will revert with the message "X is 10".if(_x == 10) {
revert("X is 10");
}
Assert:
assert function is used to test for conditions that should never fail.checkMax, we ensure that maxAmount is always 100. If it is not, the contract will halt execution with an error.assert(maxAmount == 100);
Adding a Timestamp Check:
block.timestamp to restrict function calls to within 2 days of a particular event.myFunc to ensure it can only be called within a 2-day window after the contract deployment (or after a certain point).Here’s how you can add the check in myFunc:
uint256 public startTime;
constructor() {
startTime = block.timestamp;
}
function myFunc(uint256 _x, uint256 _y) public view returns (uint256 xy) {
require(block.timestamp <= startTime + 2 days, "Function can only be called within 2 days");
require(_x < _y, "X is bigger than Y");
checkMax();
return _x + _y;
}
startTime stores the block timestamp when the contract is deployed.require(block.timestamp <= startTime + 2 days) ensures that myFunc can only be called within 2 days after the contract is deployed.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyErrorsAndChecks {
uint256 public maxAmount = 100;
uint256 public startTime;
constructor() {
startTime = block.timestamp;
}
function updateMax() public {
maxAmount = 50;
}
function myFunc(uint256 _x, uint256 _y) public view returns (uint256 xy) {
// Allow calling only within 2 days from contract deployment
require(block.timestamp <= startTime + 2 days, "Function can only be called within 2 days");
require(_x < _y, "X is bigger than Y");
checkMax();
return _x + _y;
}
function myPureRevertFunc(uint256 _x, uint256 _y) public pure returns (uint256 xy) {
if(_x == 10) {
revert("X is 10");
}
return _x + _y;
}
function checkMax() internal view {
assert(maxAmount == 100);
}
}
require is used for input validation and ensuring certain conditions are met.revert allows you to abort a transaction explicitly when certain logic fails.assert is for internal invariants that should never fail.block.timestamp for limiting function usage within a specified period.In this Solidity example, we use an enum to represent different levels of rarity and implement access control to allow only an admin to update the rarity. We’ll also modify the function to accept a parameter for setting any rarity and add a getter function to retrieve the current rarity without access control.
Enum Definition:
enum Rarity { original, rare, super_rare }: Defines three types of rarity.0 for original, 1 for rare, and 2 for super_rare.Access Control:
makeSuperRare (or any function that updates rarity) to be callable only by the admin. For simplicity, we’ll define the contract deployer as the admin.Update to Accept Rarity Type:
super_rare, we’ll modify the function to accept any Rarity type as a parameter.Get Function:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyEnums {
// Define an enum with 3 rarity types
enum Rarity {
original, // 0
rare, // 1
super_rare // 2
}
Rarity public rarity; // State variable to store the current rarity
address public admin; // Variable to store the admin address
// Constructor sets the initial rarity and defines the admin as the contract deployer
constructor() {
rarity = Rarity.rare; // Default to rare
admin = msg.sender; // Set contract deployer as admin
}
// Function to allow admin to update the rarity to any type
function makeSuperRare(Rarity _rarity) public {
require(msg.sender == admin, "Only admin can update rarity");
rarity = _rarity;
}
// Getter function to retrieve the current rarity without any access check
function getCurrentRarity() public view returns (Rarity) {
return rarity;
}
}
Admin Access Control:
require(msg.sender == admin) ensures that only the address that deployed the contract (the admin) can call the makeSuperRare function.Function to Set Any Rarity:
makeSuperRare now accepts a parameter of type Rarity:function makeSuperRare(Rarity _rarity) public {
require(msg.sender == admin, "Only admin can update rarity");
rarity = _rarity;
}
Rarity enum (either original, rare, or super_rare).Getter for Rarity:
getCurrentRarity function returns the current value of rarity without any checks.function getCurrentRarity() public view returns (Rarity) {
return rarity;
}
In this Solidity example, we explore how to use events to log important contract actions on the blockchain.
Event Declaration:
event CreatedNFT(address indexed user, uint256 id, uint256 dna); defines an event that records the address of the user who created the NFT, along with its ID and DNA.indexed keyword allows us to filter logs by the user address, making it easier to search for specific events.Emitting the Event:
createNft function, the event CreatedNFT is emitted. This generates a log on the blockchain when the function is called.msg.sender represents the address of the caller (the user creating the NFT).// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyEvents {
// Declare the event with indexed user
event CreatedNFT(address indexed user, uint256 id, uint256 dna);
// Function to create an NFT and emit the event
function createNft(uint256 _id, uint256 _dna) public {
// In real-world scenario, NFT creation logic would go here
// Emit the event to log the creation
emit CreatedNFT(msg.sender, _id, _dna);
}
}
Events:
Indexed Parameters:
indexed keyword allows the user address (msg.sender) to be searchable when querying the logs. Only up to 3 parameters can be indexed in an event.Emitting Events:
emit CreatedNFT(msg.sender, _id, _dna) broadcasts the event with the provided data, which can be accessed by off-chain applications like web3 or any blockchain explorer.CreatedNFT event to update their user interface or backend whenever an NFT is created, without having to constantly query the blockchain.This Solidity example demonstrates various types of functions in a smart contract, including pure, view, internal, external, and functions that return values.
State Variables:
myUint, myString, myBool, and myArr are state variables stored on the blockchain.uint256, string, bool, and an array of uint256.Pure Function:
myPureFunc adds two integers without accessing any contract variables.function myPureFunc(uint256 _x, uint256 _y) public pure returns (uint256 xy) {
return _x + _y;
}
View Function:
myViewFunc reads the value of myString but doesn’t change it.internal, meaning it can only be called within the contract or by derived contracts.function myViewFunc() internal view returns (string memory) {
return myString;
}
State-Modification Function:
myUpdateFunc is a public function that updates the state variable myString and uses the myViewFunc to return the updated value. This shows how internal functions can be called within the same contract.function myUpdateFunc() public returns (string memory) {
myString = "LPU";
string memory savedString = myViewFunc();
return savedString;
}
Function Returning Multiple Values:
myReturnsFunc is an external function that returns multiple state variables at once: myUint, myString, myBool, and myArr.function myReturnsFunc() external view returns (uint256, string memory, bool, uint256[] memory) {
return (myUint, myString, myBool, myArr);
}
Function with No Return:
myNoReturnFunc is an external function that simply updates the state variable myBool to false without returning any value.function myNoReturnFunc() external {
myBool = false;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyFunctions {
uint256 myUint = 42;
string myString = "Piyush";
bool myBool = true;
uint256[] myArr = [3, 3, 3];
// Pure function - No state read/write
function myPureFunc(uint256 _x, uint256 _y) public pure returns (uint256 xy) {
return _x + _y;
}
// Internal view function - Reads state, no modification
function myViewFunc() internal view returns (string memory) {
return myString;
}
// Public function - Modifies state, calls internal view function
function myUpdateFunc() public returns (string memory) {
myString = "LPU";
string memory savedString = myViewFunc();
return savedString;
}
// External function - Returns multiple state variables
function myReturnsFunc() external view returns (uint256, string memory, bool, uint256[] memory) {
return (myUint, myString, myBool, myArr);
}
// External function - Modifies state, no return
function myNoReturnFunc() external {
myBool = false;
}
}
Simple if else
In this Solidity example, we explore inheritance and function overriding. In Solidity, contracts can inherit from other contracts, and child contracts can override parent functions.
Base Contract (MyInheritance_A):
name set to "LPU".internal keyword allows this variable to be accessible in derived contracts but not from outside the contract itself.Intermediate Contract (MyInheritance_B):
MyInheritance_A and defines the getName function, which returns the value of name.virtual, which allows it to be overridden by child contracts.Derived Contract (MyInheritance_C):
MyInheritance_B.getName function and returns a different value, "Daniel".override keyword ensures that the function is recognized as an override.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Base contract
contract MyInheritance_A {
string internal name = "LPU"; // Internal variable accessible in child contracts
}
// Intermediate contract inheriting from MyInheritance_A
contract MyInheritance_B is MyInheritance_A {
// Virtual function that can be overridden in derived contracts
function getName() public view virtual returns (string memory) {
return name; // Returns the inherited name "LPU"
}
}
// Derived contract inheriting from MyInheritance_B and overriding getName
contract MyInheritance_C is MyInheritance_B {
// Override the getName function to return a different value
function getName() public view virtual override returns (string memory) {
return "Daniel"; // Returns "Daniel" instead of "LPU"
}
}
Inheritance:
MyInheritance_B inherits from MyInheritance_A, and MyInheritance_C inherits from MyInheritance_B. Each child contract has access to the parent’s state variables and functions.Function Overriding:
getName function in MyInheritance_B is marked as virtual, allowing it to be overridden in derived contracts.MyInheritance_C, the getName function is marked as override, which indicates that this function replaces the parent’s implementation.Internal Visibility:
internal keyword used for the name variable ensures that it is accessible in derived contracts (MyInheritance_B and MyInheritance_C) but not directly from outside the contract.This Solidity example demonstrates how to implement an interface using the ICounter interface and the MyInterface contract.
Interface Definition (ICounter):
ICounter interface has two functions:
count(): Returns the current counter value.addToCount(): Increases the counter by 1.Contract Implementation (MyInterface):
MyInterface contract implements the ICounter interface by providing concrete implementations for count() and addToCount().override to indicate that these functions are fulfilling the interface’s requirements.counter variable to track the count.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Interface declaration
interface ICounter {
function count() external view returns (uint256);
function addToCount() external;
}
// Contract implementing the interface
contract MyInterface is ICounter {
uint256 counter = 0; // State variable to store the count
// Override the count function to return the counter value
function count() external view override returns (uint256) {
return counter;
}
// Override the addToCount function to increment the counter
function addToCount() external override {
counter++;
}
}
Interfaces:
external and must be marked as override in the implementing contract.Implementing an Interface:
MyInterface implements the ICounter interface by providing the actual logic for both count() and addToCount().count() function is marked view because it only reads the counter value.addToCount() function updates the state by incrementing counter.Override Keyword:
override keyword to explicitly state that the function is fulfilling the interface’s definition.